% ------------------------------------------------
% May 1999
% Author: Brian J Ross
% Dept. of Computer Science, Brock University, St Catharines, ON, Canada
% Please report errors to: bross@cosc.brocku.ca

% lamarckian_evolution v.2
% 
% Performs Lamarckian evolution on P% of population, iterating 
% each K times using hill climbing.
% lamarckian_P(P, K) is set by user. Population exists as facts in
% database of form: individual(ID, Fitness, Expression).
% Fitness is standardized (0 is best, higher values are less fit).
% An improved individual, if found, is replaced in the program database.
% First clause efficiently processes entire population.
% Second case is if less than entire population to be used, in which case
% selection must be performed.


lamarckian_evolution :-	
	lamarckian_P(Percent, K),
	Percent >= 1.0,
	setof(ID, F^E^individual(ID, F, E), IDs),
	write('Lamarckian evolution...'), nl,
	lamarck_loop(IDs, K),
	!.
lamarckian_evolution :-
	lamarckian_P(Percent, K),
	Percent > 0.0,
	Percent < 1.0,
	population_size_P(PopSize),
	N is integer(Percent * PopSize),
	write('Lamarckian evolution...'), nl,
	get_unique_IDs(N, [], IDs),
	lamarck_loop(IDs, K),
	!.
lamarckian_evolution.

% get_unique_IDs(N, SoFar, IDs)
%	N - Number of unique individual ID's to collect
%	SoFar - ID's collected so far
%	IDs - final set of unique IDs
%
% Retrieves a list of N unique individual ID's, 
% selecting each one via a tournament selection routine defined elsewhere.

get_unique_IDs(0, IDs, IDs) :- !.
get_unique_IDs(N, SoFar, IDs) :-
	repeat,  
	select(ID),
	\+ member(ID, SoFar),  
	M is N - 1,
	get_unique_IDs(M, [ID|SoFar], IDs),
	!.

% lamark_loop(IDs, Iter)
%	IDs - individuals to perform Lamarckian evolution upon
%	Iter - number of iterations for best-first search
%
% The individuals in IDs have hill climbing search performed on them.
% If the result of this search is a fitter individual, then that new
% individual replaces the origianl in the database. Side effect is that
% individual/3 clauses are replaced with improved individuals when found.
% Predicate also prints + and - characters to screen to give overview of
% Lamarckian performance.

lamarck_loop([], _) :- !.
lamarck_loop([ID|Rest], Iter) :-
	individual(ID, Fit, Expr),
	hillclimb(Iter, (Fit, Expr), (NewFit, NewExpr)),
	(NewFit >= Fit -> 
		write('-')
		;
		write('+'),
		retract(individual(ID, _, _)),  
		assert(individual(ID, NewFit, NewExpr))),
	lamarck_loop(Rest, Iter),
	!.

% hillclimb(K, Item, Soln) 
%	K - iterations to perform on individual for search
%	Item - best found so far
%	Soln - The most improved solution found. Worst case is an expression
%		having same fitness as original.
%
% Does hillclimbing search for K iterations on Item.  
% Itemx contains best expression obtained so far with mutation, 
% paired with its fitness. 

hillclimb(0, Item, Item) :- !.
hillclimb(K, (TopFit, TopExpr), Soln) :- 
	mutation(TopExpr, NewExpr),
	eval_fitness(NewExpr, NewFit),
	select_best((NewFit, NewExpr), (TopFit, TopExpr), BestSoFar),	
	K2 is K - 1,
	hillclimb(K2, BestSoFar, Soln),
	!.
hill_climb(K, BestSoFar, Soln) :-
	K2 is K - 1,
	hill_climb(K2, BestSoFar, Soln),
	!.

% select best of expression pairs

select_best((F1, E1), (F2, _), (F1, E1)) :- F1 =< F2, !.
select_best(_, X, X).

